package com.ruoyi.common.utils.file;

import com.aspose.cad.Image;
import com.aspose.cad.InterpolationMode;
import com.aspose.cad.SmoothingMode;
import com.aspose.cad.TextRenderingHint;
import com.aspose.cad.fileformats.cad.CadDrawTypeMode;
import com.aspose.cad.imageoptions.CadRasterizationOptions;
import com.aspose.cad.imageoptions.SvgOptions;
import com.ruoyi.common.config.RuoYiConfig;
import com.ruoyi.common.utils.DateUtils;
import com.ruoyi.common.utils.StringUtils;
import com.ruoyi.common.utils.uuid.IdUtils;
import org.apache.batik.anim.dom.SAXSVGDocumentFactory;
import org.apache.batik.anim.dom.SVGDOMImplementation;
import org.apache.batik.svggen.SVGGraphics2D;
import org.apache.batik.transcoder.TranscoderInput;
import org.apache.batik.transcoder.TranscoderOutput;
import org.apache.batik.transcoder.svg2svg.SVGTranscoder;
import org.apache.batik.util.XMLResourceDescriptor;
import org.apache.commons.io.FilenameUtils;
import org.apache.commons.io.IOUtils;
import org.apache.commons.lang3.ArrayUtils;
import org.w3c.dom.DOMImplementation;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.NodeList;
import org.w3c.dom.svg.SVGDocument;
import org.w3c.dom.svg.SVGSVGElement;

import javax.crypto.*;
import javax.crypto.spec.SecretKeySpec;
import javax.imageio.ImageIO;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.awt.image.BufferedImage;
import java.io.*;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;

/**
 * 文件处理工具类
 *
 * @author ruoyi
 */
public class FileUtils {

    public static String FILENAME_PATTERN = "[a-zA-Z0-9_\\-\\|\\.\\u4e00-\\u9fa5]+";

    /**
     * 输出指定文件的byte数组
     *
     * @param filePath 文件路径
     * @param os       输出流
     * @return
     */
    public static void writeBytes(String filePath, OutputStream os) throws IOException {
        FileInputStream fis = null;
        try {
            File file = new File(filePath);
            if (!file.exists()) {
                throw new FileNotFoundException(filePath);
            }
            fis = new FileInputStream(file);
            byte[] b = new byte[1024];
            int length;
            while ((length = fis.read(b)) > 0) {
                os.write(b, 0, length);
            }
        } catch (IOException e) {
            throw e;
        } finally {
            IOUtils.close(os);
            IOUtils.close(fis);
        }
    }

    /**
     * 写数据到文件中
     *
     * @param data 数据
     * @return 目标文件
     * @throws IOException IO异常
     */
    public static String writeImportBytes(byte[] data) throws IOException {
        return writeBytes(data, RuoYiConfig.getImportPath());
    }

    /**
     * 写数据到文件中
     *
     * @param data      数据
     * @param uploadDir 目标文件
     * @return 目标文件
     * @throws IOException IO异常
     */
    public static String writeBytes(byte[] data, String uploadDir) throws IOException {
        FileOutputStream fos = null;
        String pathName = "";
        try {
            String extension = getFileExtendName(data);
            pathName = DateUtils.datePath() + "/" + IdUtils.fastUUID() + "." + extension;
            File file = FileUploadUtils.getAbsoluteFile(uploadDir, pathName);
            fos = new FileOutputStream(file);
            fos.write(data);
        } finally {
            IOUtils.close(fos);
        }
        return FileUploadUtils.getPathFileName(uploadDir, pathName);
    }

    /**
     * 删除文件
     *
     * @param filePath 文件
     * @return
     */
    public static boolean deleteFile(String filePath) {
        boolean flag = false;
        File file = new File(filePath);
        // 路径为文件且不为空则进行删除
        if (file.isFile() && file.exists()) {
            flag = file.delete();
        }
        return flag;
    }

    /**
     * 文件名称验证
     *
     * @param filename 文件名称
     * @return true 正常 false 非法
     */
    public static boolean isValidFilename(String filename) {
        return filename.matches(FILENAME_PATTERN);
    }

    /**
     * 检查文件是否可下载
     *
     * @param resource 需要下载的文件
     * @return true 正常 false 非法
     */
    public static boolean checkAllowDownload(String resource) {
        // 禁止目录上跳级别
        if (StringUtils.contains(resource, "..")) {
            return false;
        }

        // 检查允许下载的文件规则
        if (ArrayUtils.contains(MimeTypeUtils.DEFAULT_ALLOWED_EXTENSION, FileTypeUtils.getFileType(resource))) {
            return true;
        }

        // 不在允许下载的文件规则
        return false;
    }

    /**
     * 下载文件名重新编码
     *
     * @param request  请求对象
     * @param fileName 文件名
     * @return 编码后的文件名
     */
    public static String setFileDownloadHeader(HttpServletRequest request, String fileName) throws UnsupportedEncodingException {
        final String agent = request.getHeader("USER-AGENT");
        String filename = fileName;
        if (agent.contains("MSIE")) {
            // IE浏览器
            filename = URLEncoder.encode(filename, "utf-8");
            filename = filename.replace("+", " ");
        } else if (agent.contains("Firefox")) {
            // 火狐浏览器
            filename = new String(fileName.getBytes(), "ISO8859-1");
        } else if (agent.contains("Chrome")) {
            // google浏览器
            filename = URLEncoder.encode(filename, "utf-8");
        } else {
            // 其它浏览器
            filename = URLEncoder.encode(filename, "utf-8");
        }
        return filename;
    }

    /**
     * 下载文件名重新编码
     *
     * @param response     响应对象
     * @param realFileName 真实文件名
     */
    public static void setAttachmentResponseHeader(HttpServletResponse response, String realFileName) throws UnsupportedEncodingException {
        String percentEncodedFileName = percentEncode(realFileName);

        StringBuilder contentDispositionValue = new StringBuilder();
        contentDispositionValue.append("attachment; filename=").append(percentEncodedFileName).append(";").append("filename*=").append("utf-8''").append(percentEncodedFileName);

        response.addHeader("Access-Control-Expose-Headers", "Content-Disposition,download-filename");
        response.setHeader("Content-disposition", contentDispositionValue.toString());
        response.setHeader("download-filename", percentEncodedFileName);
    }

    /**
     * 百分号编码工具方法
     *
     * @param s 需要百分号编码的字符串
     * @return 百分号编码后的字符串
     */
    public static String percentEncode(String s) throws UnsupportedEncodingException {
        String encode = URLEncoder.encode(s, StandardCharsets.UTF_8.toString());
        return encode.replaceAll("\\+", "%20");
    }

    /**
     * 获取图像后缀
     *
     * @param photoByte 图像数据
     * @return 后缀名
     */
    public static String getFileExtendName(byte[] photoByte) {
        String strFileExtendName = "jpg";
        if ((photoByte[0] == 71) && (photoByte[1] == 73) && (photoByte[2] == 70) && (photoByte[3] == 56) && ((photoByte[4] == 55) || (photoByte[4] == 57)) && (photoByte[5] == 97)) {
            strFileExtendName = "gif";
        } else if ((photoByte[6] == 74) && (photoByte[7] == 70) && (photoByte[8] == 73) && (photoByte[9] == 70)) {
            strFileExtendName = "jpg";
        } else if ((photoByte[0] == 66) && (photoByte[1] == 77)) {
            strFileExtendName = "bmp";
        } else if ((photoByte[1] == 80) && (photoByte[2] == 78) && (photoByte[3] == 71)) {
            strFileExtendName = "png";
        }
        return strFileExtendName;
    }

    /**
     * 获取文件名称 /profile/upload/2022/04/16/ruoyi.png -- ruoyi.png
     *
     * @param fileName 路径名称
     * @return 没有文件路径的名称
     */
    public static String getName(String fileName) {
        if (fileName == null) {
            return null;
        }
        int lastUnixPos = fileName.lastIndexOf('/');
        int lastWindowsPos = fileName.lastIndexOf('\\');
        int index = Math.max(lastUnixPos, lastWindowsPos);
        return fileName.substring(index + 1);
    }

    /**
     * 获取不带后缀文件名称 /profile/upload/2022/04/16/ruoyi.png -- ruoyi
     *
     * @param fileName 路径名称
     * @return 没有文件路径和后缀的名称
     */
    public static String getNameNotSuffix(String fileName) {
        if (fileName == null) {
            return null;
        }
        String baseName = FilenameUtils.getBaseName(fileName);
        return baseName;
    }

    /**
     * 构建过滤单文件地址
     *
     * @param url 单文件地址
     * @return 返回过滤后的单文件地址
     * @author KevenPotter
     * @date 2021-06-02 23:43:12
     */
    public static String buildFileUrl(String url) {
        return url.substring(url.indexOf("/upload/"));
    }

    /**
     * 构建过滤多文件地址
     *
     * @param fileUrls 多文件地址数组
     * @return 返回过滤后的多文件地址
     * @author KevenPotter
     * @date 2021-06-02 23:43:55
     */
    public static String buildFilesUrl(String[] fileUrls) {
        if (fileUrls != null && fileUrls.length > 0) {
            StringBuilder files = new StringBuilder();
            for (String url : fileUrls) {
                url = url.substring(url.indexOf("/upload"));
                files.append(url).append(",");
            }
            return StringUtils.removeTheLastDigit(files);
        } else {
            return null;
        }
    }

    /**
     * dwg转svg
     *
     * @param fileAddress 文件地址
     * @param fileName    文件名称
     * @return 返回转换后的svg文件地址
     * @author KevenPotter
     * @date 2024-06-12 15:34:18
     */
    public static String dwg2svg(String fileAddress, String fileName) {
        String directory = fileAddress.substring(0, fileAddress.lastIndexOf('/') + 1);
        String convertFileName = fileName + ".svg";
        Image cadImage = Image.load(fileAddress);
        CadRasterizationOptions rasterizationOptions = new CadRasterizationOptions();
        rasterizationOptions.setPageWidth(1500);
        rasterizationOptions.setPageHeight(756);
        rasterizationOptions.setAutomaticLayoutsScaling(true);
        rasterizationOptions.setNoScaling(false);
        rasterizationOptions.setContentAsBitmap(true);
        rasterizationOptions.setLayouts(new String[]{"Model"});
        rasterizationOptions.setDrawType(CadDrawTypeMode.UseObjectColor);

        SvgOptions svgOptions = new SvgOptions();
        svgOptions.setVectorRasterizationOptions(rasterizationOptions);

        // Set Graphics options
        rasterizationOptions.getGraphicsOptions().setSmoothingMode(SmoothingMode.HighQuality);
        rasterizationOptions.getGraphicsOptions().setTextRenderingHint(TextRenderingHint.AntiAliasGridFit);
        rasterizationOptions.getGraphicsOptions().setInterpolationMode(InterpolationMode.HighQualityBicubic);
        String svgFileAddress = directory + convertFileName;
        // Export to PDF by calling the Save method
        cadImage.save(directory + convertFileName, svgOptions);
        return svgFileAddress;
    }

    /**
     * 去除svg文件的水印
     *
     * @param wmFileAddress 带有水印的svg文件
     * @return 返回转换后的无水印svg文件地址
     * @author KevenPotter
     * @date 2024-06-12 15:37:26
     */
    public static String rfSvgWatermark(String wmFileAddress) {
        try {
            File inputFile = new File(wmFileAddress);
            File outFile = new File(wmFileAddress);

            String parser = XMLResourceDescriptor.getXMLParserClassName();
            SAXSVGDocumentFactory f = new SAXSVGDocumentFactory(parser);
            String uri = inputFile.toURI().toString();
            SVGDocument svgDocument = f.createSVGDocument(uri);
            SVGSVGElement root = svgDocument.getRootElement();

            NodeList childNodes = root.getChildNodes();
            root.removeChild(childNodes.item(3));
            TranscoderInput input = new TranscoderInput(svgDocument);
            SVGTranscoder t = new SVGTranscoder();
            try (OutputStream os = new FileOutputStream(outFile)) {
                OutputStreamWriter writer = new OutputStreamWriter(os, StandardCharsets.UTF_8);
                TranscoderOutput output = new TranscoderOutput(writer);
                t.transcode(input, output);
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
        return wmFileAddress;
    }

    /**
     * png转svg
     *
     * @param pngFilePath png文件地址
     * @param svgFilePath svg文件地址
     * @author KevenPotter
     * @date 2024-06-21 23:22:17
     */
    public static void png2Svg(String pngFilePath, String svgFilePath) {
        File pngFile = new File(pngFilePath);
        File svgFile = new File(svgFilePath);
        BufferedImage image;
        try {
            image = ImageIO.read(pngFile);
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
        BufferedImage convertedImage = new BufferedImage(image.getWidth(), image.getHeight(), BufferedImage.TYPE_INT_RGB);
        convertedImage.createGraphics().drawImage(image, 0, 0, null);

        // 创建SVG文档
        DOMImplementation domImpl = SVGDOMImplementation.getDOMImplementation();
        String svgNS = SVGDOMImplementation.SVG_NAMESPACE_URI;
        Document document = domImpl.createDocument(svgNS, "svg", null);
        Element root = document.getDocumentElement();
        root.setAttributeNS(null, "width", String.valueOf(convertedImage.getWidth()));
        root.setAttributeNS(null, "height", String.valueOf(convertedImage.getHeight()));

        // 转换为SVG代码
        SVGGraphics2D svgGenerator = new SVGGraphics2D(document);
        svgGenerator.drawImage(convertedImage, null, null);
        try (Writer writer = new FileWriter(svgFile)) {
            svgGenerator.stream(writer);
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }

    /**
     * 文件加密
     *
     * @param secretKey           文件加密密钥
     * @param sourceFilePath      需要加密文件地址
     * @param destinationFilePath 加密后文件地址
     * @author KevenPotter
     */
    public static void encryptFile(String secretKey, String sourceFilePath, String destinationFilePath) throws NoSuchAlgorithmException, NoSuchPaddingException, InvalidKeyException, IOException {
        // 使用密钥字符串生成秘密密钥
        SecretKey secretKeySpec = new SecretKeySpec(secretKey.getBytes(), "AES");
        // 获取 AES 加密算法的实例
        Cipher cipher = Cipher.getInstance("AES");
        // 使用秘密密钥初始化密码 cipher，设置为加密模式
        cipher.init(Cipher.ENCRYPT_MODE, secretKeySpec);

        // 获取源文件路径
        Path sourcePath = Paths.get(sourceFilePath);
        // 获取目标加密文件路径
        Path destinationPath = Paths.get(destinationFilePath);

        // 创建输入流，读取源文件
        try (InputStream inputStream = Files.newInputStream(sourcePath);
             // 创建输出流，写入加密文件
             OutputStream outputStream = Files.newOutputStream(destinationPath);
             // 创建密码输出流，连接到输出流，并使用密码 cipher 进行加密
             CipherOutputStream cipherOutputStream = new CipherOutputStream(outputStream, cipher)) {
            // 缓冲区大小
            byte[] buffer = new byte[4096];
            int bytesRead;
            // 读取源文件内容到缓冲区
            while ((bytesRead = inputStream.read(buffer)) != -1) {
                // 将加密后的数据写入加密文件
                cipherOutputStream.write(buffer, 0, bytesRead);
            }
        }
    }

    /**
     * 文件解密
     *
     * @param secretKey           文件解密密钥
     * @param sourceFilePath      需要解密的文件地址
     * @param destinationFilePath 解密后的文件地址
     * @author KevenPotter
     */
    public static void decryptFile(String secretKey, String sourceFilePath, String destinationFilePath) throws NoSuchAlgorithmException, NoSuchPaddingException, InvalidKeyException, IOException {
        SecretKey secretKeySpec = new SecretKeySpec(secretKey.getBytes(), "AES"); // 使用密钥字符串生成秘密密钥
        Cipher cipher = Cipher.getInstance("AES"); // 获取 AES 加密算法的实例
        cipher.init(Cipher.DECRYPT_MODE, secretKeySpec); // 使用秘密密钥初始化密码 cipher，设置为解密模式

        Path sourcePath = Paths.get(sourceFilePath); // 获取源加密文件路径
        Path destinationPath = Paths.get(destinationFilePath); // 获取目标解密文件路径

        try (InputStream inputStream = Files.newInputStream(sourcePath); // 创建输入流，读取加密文件
             OutputStream outputStream = Files.newOutputStream(destinationPath); // 创建输出流，写入解密文件
             CipherInputStream cipherInputStream = new CipherInputStream(inputStream, cipher)) { // 创建密码输入流，连接到输入流，并使用密码 cipher 进行解密
            byte[] buffer = new byte[4096]; // 缓冲区大小
            int bytesRead;
            while ((bytesRead = cipherInputStream.read(buffer)) != -1) { // 读取加密文件内容到缓冲区
                outputStream.write(buffer, 0, bytesRead); // 将解密后的数据写入解密文件
            }
        }
    }

}
